﻿using Oracle.ManagedDataAccess.Client;
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Text;

namespace Moon.Sql
{
    public class OracleDynamicParameters : IDynamicParameters
    {
        private readonly List<OracleParameter> _sqlParameters = new List<OracleParameter>();
        private readonly Dictionary<Type, OracleDbType> TypeMap;

        #region OracleDynamicParameters
        public OracleDynamicParameters()
        {
            TypeMap = new Dictionary<Type, OracleDbType>()
            {
                [typeof(byte)] = OracleDbType.Byte,
                [typeof(short)] = OracleDbType.Int16,
                [typeof(int)] = OracleDbType.Int32,
                [typeof(long)] = OracleDbType.Int64,
                [typeof(float)] = OracleDbType.Single,
                [typeof(double)] = OracleDbType.Double,
                [typeof(decimal)] = OracleDbType.Decimal,
                [typeof(bool)] = OracleDbType.Boolean,
                [typeof(string)] = OracleDbType.Varchar2,
                [typeof(char)] = OracleDbType.Char,
                [typeof(DateTime)] = OracleDbType.Date,
                [typeof(TimeSpan)] = OracleDbType.TimeStamp,
                [typeof(byte[])] = OracleDbType.Blob,
                [typeof(byte?)] = OracleDbType.Byte,
                [typeof(short?)] = OracleDbType.Int16,
                [typeof(int?)] = OracleDbType.Int32,
                [typeof(long?)] = OracleDbType.Int64,
                [typeof(float?)] = OracleDbType.Single,
                [typeof(double?)] = OracleDbType.Double,
                [typeof(decimal?)] = OracleDbType.Decimal,
                [typeof(bool?)] = OracleDbType.Boolean,
                [typeof(char?)] = OracleDbType.Char,
                [typeof(DateTime?)] = OracleDbType.Date,
                [typeof(TimeSpan?)] = OracleDbType.TimeStamp,
                [typeof(object)] = OracleDbType.Varchar2
            };
        }
        #endregion OracleDynamicParameters

        public void Add(string name, object value)
        {
            Add(name, value, direction: ParameterDirection.Input);
        }

        public void Add(DynamicParameters parameters)
        {
            foreach (KeyValuePair<string, ParamInfo> item in parameters.Get())
            {
                Add(item.Key, item.Value.Value);
            }
        }

        public void Add(string name, object value, Type type)
        {
            var oracleParameter = new OracleParameter(name, LoadDbType(type));
            oracleParameter.Direction = ParameterDirection.Input;
            oracleParameter.Value = value;
            _sqlParameters.Add(oracleParameter);
        }

        public void Add(string name, object value = null, OracleDbType dbType = OracleDbType.Varchar2, ParameterDirection direction = ParameterDirection.Input, int? size = null)
        {
            var oracleParameter = new OracleParameter(name, value)
            {
                OracleDbType = dbType,
                Direction = direction
            };
            if (size.HasValue)
            {
                oracleParameter.Size = Convert.ToInt32(size);
            }
            _sqlParameters.Add(oracleParameter);
        }

        public void Add(string name, OracleDbType oracleDbType, ParameterDirection direction = ParameterDirection.Input)
        {
            var oracleParameter = new OracleParameter(name, oracleDbType) { Direction = direction };
            _sqlParameters.Add(oracleParameter);
        }

        public void Add(string name, OracleDbType oracleDbType, int size, ParameterDirection direction = ParameterDirection.Input)
        {
            var oracleParameter = new OracleParameter(name, oracleDbType, size) { Direction = direction };
            _sqlParameters.Add(oracleParameter);
        }

        public void AddParameters(IDbCommand cmd)
        {
            (cmd as OracleCommand).Parameters.AddRange(_sqlParameters.ToArray());
        }

        public object Get(string name)
        {
            var parameter = _sqlParameters.FirstOrDefault(t => t.ParameterName == name);
            if (parameter != null)
                return parameter.Value;
            return null;
        }

        public override string ToString()
        {
            StringBuilder builder = new StringBuilder();
            foreach (var item in _sqlParameters)
            {
                builder.Append(item.ParameterName).Append("=").Append((item.Value == null || item.Value is DBNull) ? "NULL" : item.Value).Append(",");
            }
            return builder.Length == 0 ? "" : builder.ToString(0, builder.Length - 1);
        }

        public void Clear()
        {
            _sqlParameters.Clear();
        }

        public bool Any()
        {
            return _sqlParameters.Any();
        }

        public OracleDbType LoadDbType(Type type)
        {
            var nullUnderlyingType = Nullable.GetUnderlyingType(type);
            if (nullUnderlyingType != null) type = nullUnderlyingType;
            if (type.IsEnum && !TypeMap.ContainsKey(type))
            {
                type = Enum.GetUnderlyingType(type);
            }
            if (TypeMap.TryGetValue(type, out OracleDbType dbType))
            {
                return dbType;
            }
            return OracleDbType.Varchar2;
        }
    }
}